#pragma once

#include "stdafx.h"

class Pairs
{
public:
	Pairs(int _row, int _col) : row(_row), col(_col) {};
	int row, col;
private:
	
};

class Matrix 
{
private:
	uint64 chunks;
	uint64* chunkDim;
	uint64 chunkMean;
	float** _data;
	uint64 rows;
	uint64 cols;
	uint64 count;
public:
	Matrix(uint64 nrows, uint64 ncols, uint64 nchunks = 1)
		: rows(nrows), cols(ncols), chunks(nchunks), count(nrows * ncols), chunkMean((int) ceil((float)nrows * (float)ncols / (float)nchunks))
	{
		std::cout << "creating DenseMatrix\n";
		chunkDim = new uint64[chunks];
		uint64 left = count;
		_data = new (float*[chunks]);
		int c = 0;
		for (uint64 i = 0; i < chunks; i++)
		{
			chunkDim[i] = left >= chunkMean ? chunkMean : left;
			left -= chunkDim[i];			
			//std::cout << chunkDim[i] << ":" << c++ << "\n";
			_data[i] = new float[chunkDim[i]];
		}
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < cols; j++)
			{
				uint64 nth = i * cols + j;
				uint64 chunck_ord = nth / chunkMean;
		
				_data[chunck_ord][nth % chunkMean] = 0;
			}
		}
	}
	inline uint64 GetCols()
	{
		return cols;
	}
	inline ~Matrix()
	{
		for (uint64 i = 0; i < chunks; i++)
		{
			delete[] _data[i];
		}
		delete[] _data;
		delete[] chunkDim;
	}
	inline const float& operator[](const Pairs idx) 
	{
		uint64 nth = idx.row * cols + idx.col;
		uint64 chunck_ord = nth / chunkMean;
		return _data[chunck_ord][nth % chunkMean];
	}

	inline float& operator() (const uint64 _row, const uint64 _col)
	{
		uint64 nth = _row * cols + _col;
		uint64 chunck_ord = nth / chunkMean;
		
		return _data[chunck_ord][nth % chunkMean];
	}

	inline float operator() (const uint64 _row, const uint64 _col) const
	{
		uint64 nth = _row * cols + _col;
		uint64 chunck_ord = nth / chunkMean;
		
		return _data[chunck_ord][nth % chunkMean];
	}

	static Matrix* Identity(uint64 dim, uint64 chunks = 1)
	{
		Matrix* res = new Matrix(dim, dim, chunks);
		for (int i = 0; i < dim; i++)
		{
			for (int j = 0; j < dim; j++)
			{
				if(i == j)
					(*res)(i,j) = 1;
			}
		}
		return res;
	}

	void PrintMatrix()
	{
		std::string res = "";
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < cols; j++)
			{
				uint64 nth = i * cols + j;
				uint64 chunck_ord = nth / chunkMean;
		
				std::cout << _data[chunck_ord][nth % chunkMean] << "; ";
			}
			std::cout << "\n";
		}
	}
};

class ChunkedMatrix 
{
private:
	uint64 chunks;
	uint64* chunkRows;
	uint64 chunkMean;
	float** _data;
	uint64 rows;
	uint64 cols;
	uint64 count;
public:
	ChunkedMatrix(uint64 nrows, uint64 ncols, uint64 nchunks = 1)
		: rows(nrows), cols(ncols), chunks(nchunks), count(nrows * ncols), chunkMean((int) ceil((float)nrows / (float)nchunks) )
	{

		std::cout << "creating chunkedMatrix\nchunck size = " << chunkMean << "\n";
		chunkRows = new uint64[chunks];
		uint64 left = nrows;
		_data = (float**) malloc(sizeof(float*) * chunks); //new (float*[chunks]);
		for (uint64 i = 0; i < chunks; i++)
		{
			chunkRows[i] = left >= chunkMean ? chunkMean : left;
			left -= chunkRows[i];			
			//std::cout << chunkDim[i] << ":" << c++ << "\n";
			_data[i] = (float*) malloc(chunkRows[i] * cols * sizeof(float));
			//_data[i] = new float[chunkRows[i] * cols];
		}

		for (uint64 i = 0; i < chunks; i++)
		{
			float* current = _data[i];
			for (uint64 j = 0; j < chunkRows[i] * cols; j++)
			{
				current[j] = 0;
			}
		}
		//this->PrintMatrix();
		//for (int i = 0; i < rows; i++)
		//{
		//	for (int j = 0; j < cols; j++)
		//	{
		//		uint64 nth = i * cols + j;
		//		uint64 chunck_ord = nth / chunkMean;
		//
		//		_data[chunck_ord][nth % chunkMean] = 0;
		//	}
		//}
	}
	inline uint64 GetCols()
	{
		return cols;
	}
	inline ~ChunkedMatrix()
	{
		for (uint64 i = 0; i < chunks; i++)
		{
			delete[] _data[i];
		}
		delete[] _data;
		delete[] chunkRows;
	}
	//inline const float& operator[](const Pairs idx) 
	//{
	//	uint64 nth = idx.row * cols + idx.col;
	//	uint64 chunck_ord = nth / chunkMean;
	//	return _data[chunck_ord][nth % chunkMean];
	//}

	void Test() 
	{
		uint64 noZero = 0;
		for (int i = 0; i < chunks; i++)
		{
			for (int j = 0; j < chunkRows[i]; j++)
			{
				if (_data[i][j] != 0)
					noZero++;
			}
		}
		std::cout << "Count: " << count << " Non zero elements: " << noZero << " Zero: " << count - noZero << "\n\n";
	}

	inline float& operator() (const uint64 _row, const uint64 _col)
	{
		uint64 nth = (_row % chunkMean * cols )+ _col;
		uint64 chunck_ord = _row / chunkMean;
		
		return _data[chunck_ord][nth];
	}

	inline float operator() (const uint64 _row, const uint64 _col) const
	{
		uint64 nth = (_row % chunkMean * cols )+ _col;
		uint64 chunck_ord = _row / chunkMean;
		
		return _data[chunck_ord][nth];
	}

	void MakeStochastic(uint64* outLink)
	{
		for (int i = 0; i < rows; i++)
		{
			if (outLink[i] == 0)
			{
				for (int j = 0; j < rows; j++)
				{
					uint64 nth = (i % chunkMean * cols )+ j;
					uint64 chunck_ord = i / chunkMean;
					_data[chunck_ord][nth] = 1.0f / (float) rows;
				}
			}
			else
			{
				for (int j = 0; j < rows; j++)
				{
					uint64 nth = (i % chunkMean * cols )+ j;
					uint64 chunck_ord = i / chunkMean;
					auto elem = _data[chunck_ord][nth];
					if (elem != 0)
					{
						_data[chunck_ord][nth] = 1.0f / (float) outLink[i];
					}
				}
			}
		}
	}

	static Matrix* Identity(uint64 dim, uint64 chunks = 1)
	{
		Matrix* res = new Matrix(dim, dim, chunks);
		for (int i = 0; i < dim; i++)
		{
			for (int j = 0; j < dim; j++)
			{
				if(i == j)
					(*res)(i,j) = 1;
			}
		}
		return res;
	}

	inline void MultiplyByChunks(float* p, float* res)
	{
		std::cout << "\n\ntrying sequential multiplication\n";
		for (int i = 0; i < cols; i++)
		{
			res[i] = 0;
		}

		for (uint64 c = 0; c < chunks; c++)
		{
			uint64 c_rows = chunkRows[c];
			float* current_chunk = _data[c];
			uint64 res_offset = c * chunkMean;

			//std::cout << "chunk: " << c << " rows:" << c_rows << " res_offset: " << res_offset << ";\n" ;

			for (uint64 i = 0; i < c_rows; i++)
			{
				for (uint64 j = 0; j < cols; j++)
				{
					uint64 nth = i * cols + j;
					//std::cout << "res_ptr " << res_offset + i << " nth: " << nth;
					res[j] += current_chunk[nth] * p[res_offset + i];
				}
			}
		}
	}

	void PrintMatrix()
	{
		for (uint64 c = 0; c < chunks; c++)
		{
			uint64 c_rows = chunkRows[c];
			float* current_chunk = _data[c];
			uint64 res_offset = c * chunkMean;

			for (uint64 i = 0; i < c_rows; i++)
			{
				for (uint64 j = 0; j < cols; j++)
				{
					uint64 nth = i * cols + j;
					//std::cout << "res_ptr " << res_offset + i << " nth: " << nth;
					std::cout << current_chunk[nth] << ";";
				}
				std::cout << "\n";
			}
		}
		
		/*
		for (int i = 0; i < rows; i++)
		{
			for (int j = 0; j < cols; j++)
			{
				uint64 nth = i * cols + j;
				uint64 chunck_ord = nth / chunkMean;
		
				std::cout << _data[chunck_ord][nth % chunkMean] << "; ";
			}
			std::cout << "\n";
		}*/
	}
};
